package cn.iocoder.yudao.framework.pay.core.client.impl.huifu;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.pay.core.client.dto.huifu.UserBasicdataRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.huifu.UserBusiOpenRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.huifu.UserInfoReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.huifu.bspay.sdk.opps.client.BasePayClient;
import com.huifu.bspay.sdk.opps.core.BasePay;
import com.huifu.bspay.sdk.opps.core.request.*;
import com.huifu.bspay.sdk.opps.core.utils.DateTools;
import com.huifu.bspay.sdk.opps.core.utils.SequenceTools;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN;
import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;

/**
 * 汇付银行卡转账
 */
@Slf4j
public class HuifuBankPayClient extends AbstractHuifuPayClient {

    // 在 cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl——put引用
    public HuifuBankPayClient(Long channelId, HuifuWxPayClientConfig config) {
        super(channelId, PayChannelEnum.BANK.getCode(), config);
    }

    /**
     * 初始化支付的配置
     */
    @Override
    protected void doInit() {
        super.doInit(Boolean.TRUE, BasePay.MODE_PROD);
    }

    /**
     * 统一下单（汇付 JSPay 接口）
     */
    @Override
    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable {
        // 1. 构建支付请求参数对象（V2TradePaymentJspayRequest 是汇付下单请求模型）
        V2TradePaymentJspayRequest v2TradePaymentJspayRequest = buildJsPayRequest(reqDTO);
        log.info("[doUnifiedOrder][构建请求参数: {}]", v2TradePaymentJspayRequest);
        // 2. 发起请求，调用汇付接口，返回数据为 Map<String, Object>
        Map<String, Object> response = doExecute(v2TradePaymentJspayRequest);
        // 3. 提取 pay_info 字段（内为支付配置参数，如 appId、prepayId 等）
        // 该字段本身是字符串形式的 JSON
        String payInfoJson = (String) response.get("pay_info");
        // 4. 转换 pay_info 为 JSONObject 再转为字符串，保障格式统一
        JSONObject payInfoObject = JSONObject.parseObject(payInfoJson);
        String payInfoStr = payInfoObject.toJSONString(); // 此处是返回给前端的 pay 配置参数字符串
        // 5. 构建统一返回 DTO（含：显示模式 + payInfo + 订单号 + 原始响应）
        return PayOrderRespDTO.waitingOf(
                PayOrderDisplayModeEnum.APP.getMode(), // 展示模式：APP 模式
                payInfoStr,                            // 前端需要使用的 pay 参数配置
                reqDTO.getOutTradeNo(),                // 外部交易单号
                response                               // 原始响应内容（可选传给前端或日志记录）
        );
    }

    private V2TradePaymentJspayRequest buildJsPayRequest(PayOrderUnifiedReqDTO reqDTO) {
        V2TradePaymentJspayRequest request = new V2TradePaymentJspayRequest();
        // 基础参数
        request.setReqDate(DateTools.getCurrentDateYYYYMMDD());// 请求日期
//        request.setReqSeqId(SequenceTools.getReqSeqId32());// 请求流水号
        request.setReqSeqId(reqDTO.getOutTradeNo());// 请求流水号
        request.setHuifuId(config.getHuifuId());// 商户号
        request.setTradeType("T_MINIAPP");// 交易类型
        request.setGoodsDesc(reqDTO.getBody());// 商品描述

        // 金额处理，保留2位小数
        String transAmt = BigDecimal.valueOf(reqDTO.getPrice())
                .divide(BigDecimal.valueOf(100))
                .setScale(2, RoundingMode.HALF_UP)
                .toString();
        request.setTransAmt(transAmt);

        // 扩展字段封装
        Map<String, Object> extendInfoMap = new HashMap<>();
//        extendInfoMap.put("time_expire", DateTools.formatYYYYMMDDHHSS(order.getExpireTime()));// 交易有效期
        extendInfoMap.put("limit_pay_type", "NO_CREDIT");// 禁用信用卡标记
        extendInfoMap.put("delay_acct_flag", "N"); // 是否延迟交易
        extendInfoMap.put("fq_mer_discount_flag", "N"); // 商户贴息标记
        extendInfoMap.put("notify_url", reqDTO.getNotifyUrl());// 异步通知地址
        extendInfoMap.put("remark", reqDTO.getBody()); // 备注

        // 构建 wx_data 对象
        JSONObject wxData = new JSONObject();
        wxData.put("sub_appid", config.getAppId());// 子商户公众账号id
        wxData.put("sub_openid", getOpenid(reqDTO));// 子商户用户标识
        extendInfoMap.put("wx_data", wxData.toJSONString());// 聚合正扫微信拓展参数集合

        request.setExtendInfo(extendInfoMap);
        return request;
    }

    /**
     * 解析汇付回调的支付订单通知
     *
     * @param params  回调参数 Map
     * @param body    请求体（未使用）
     * @param headers 请求头（未使用）
     * @return 支付订单响应 DTO
     * @throws Throwable 异常
     */
    @Override
    protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
        if (ObjUtil.notEqual(params.get("resp_code"), "00000000")) {
            throw new ServiceException(500, "汇支付网关返回码异常");
        }
        // 1. 获取并解析 resp_data 字段
        String respData = params.get("resp_data");
        if (StrUtil.isBlank(respData)) {
            throw new ServiceException(500, "回调参数缺少 resp_data 字段");
        }

        JSONObject response;
        try {
            response = JSONObject.parseObject(respData);
        } catch (Exception e) {
            throw new ServiceException(500, "resp_data JSON 解析失败：" + respData);
        }

        // 2. 判断交易状态：S 成功，否则为已关闭
        Integer status = "S".equals(response.getString("trans_stat"))
                ? PayOrderStatusRespEnum.SUCCESS.getStatus()
                : PayOrderStatusRespEnum.CLOSED.getStatus();

        // 3. 构建响应 DTO
        return PayOrderRespDTO.of(
                status,
                response.getString("hf_seq_id"),
                response.getString("wx_user_id"),
                parseDate(response.getString("end_time")),
                response.getString("req_seq_id"),
                params
        );
    }

    /**
     * 获得支付订单信息
     * outTradeNo pay_order_extension订单号
     */
    @Override
    protected PayOrderRespDTO doGetOrder(String outTradeNo, LocalDateTime createTime) throws Throwable {
        V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
        // 原机构请求日期
        String reqDate = TemporalAccessorUtil.format(createTime.atZone(ZoneId.systemDefault()), PURE_DATE_PATTERN);
        request.setOrgReqDate(reqDate);
        // 服务订单创建请求流水号
        request.setOrgReqSeqId(outTradeNo);
        request.setHuifuId(config.getHuifuId());  // 商户号
        Map<String, Object> extendInfoMap = new HashMap<>(); // 非必填字段
        request.setExtendInfo(extendInfoMap);
        // 构建 request 请求参数
        Map<String, Object> response = doExecute(request);
        log.info("[汇付支付交易查询][结果参数({})]", response);
        if (isSuccess(response)) {
            if ("S".equals(response.get("trans_stat"))) {
                return PayOrderRespDTO.successOf(
                        response.get("org_hf_seq_id").toString(),// 交易返回的全局流水号
                        response.get("wx_user_id").toString(),
                        parseDate(response.get("end_time").toString()),
                        response.get("org_req_seq_id").toString(), // 原机构请求流水号
                        response);
            }
        }
        return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response),
                response.get("org_req_seq_id").toString(), response);
    }

    static LocalDateTime parseDate(String time) {
        return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN);
    }

    @Override
    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
        throw new UnsupportedOperationException("汇付执行统一退款未实现");
    }

    @Override
    protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
        throw new UnsupportedOperationException("汇付支付退款回调待实现");
    }

    @Override
    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo, LocalDateTime reqTime) {
        throw new UnsupportedOperationException("汇付获得退款订单信息未实现");
    }

    /**
     * 转账订单回调通知
     */
    @Override
    protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
        // 0. 校验汇付网关返回码（必须为成功）
        if (ObjUtil.notEqual(params.get("resp_code"), "00000000")) {
            throw new ServiceException(500, "【转账】汇支付网关返回码异常: resp_code=" + params.get("resp_code"));
        }
        // 1. 获取并解析 resp_data 字段
        String respData = params.get("resp_data");
        if (StrUtil.isBlank(respData)) {
            throw new ServiceException(500, "【转账回调】缺少 resp_data 字段");
        }
        JSONObject response = JSONObject.parseObject(respData);
        log.info("[汇付转账回调查询][结果参数({})]", response);

        // 2. 提取核心参数
        String transStat = Convert.toStr(response.get("trans_status")); // 交易状态（P-处理中、S-成功、F-失败）
        String hfSeqId = Convert.toStr(response.get("hf_seq_id"));      // 汇付返回的全局流水号
//  String endTime = Convert.toStr(response.get("end_time"));       // 原返回的交易完成时间（弃用）
        String endTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); // 当前系统时间作为交易完成时间
        String reqSeq = Convert.toStr(response.get("req_seq_id"));      // 原机构请求流水号
        String respCode = Convert.toStr(response.get("sub_resp_code")); // 业务返回码
        String respDesc = Convert.toStr(response.get("sub_resp_desc")); // 业务返回描述

        // 2. 判断交易状态：
        if ("P".equals(transStat)) { // 处理中
            return PayTransferRespDTO.dealingOf(hfSeqId, reqSeq, params);
        } else if ("S".equals(transStat)) { // 成功
            return PayTransferRespDTO.successOf(hfSeqId, parseDate(endTime), reqSeq, params);
        } else if ("F".equals(transStat)) { // 失败
            return PayTransferRespDTO.closedOf(respCode, respDesc, reqSeq, params);
        } else {
            throw new ServiceException(500, "【转账回调】交易状态无效: " + transStat);
        }
    }

    /**
     * 查询转账订单状态
     *
     * @param outTradeNo 转账请求流水号（原机构请求流水号）
     * @param createTime 转账发起时间
     * @param huifuId    汇付用户ID
     * @param type       转账类型（余额转账、银行卡转账等）
     * @return PayTransferRespDTO 转账查询结果
     */
    @Override
    protected PayTransferRespDTO doGetTransfer(String outTradeNo, LocalDateTime createTime, String huifuId, PayTransferTypeEnum type) throws Throwable {
        // 1.查询取现接口
        V2TradeSettlementQueryRequest request = new V2TradeSettlementQueryRequest();
        request.setHuifuId(huifuId); // 汇付客户ID
        // 原交易请求日期（格式：yyyyMMdd）
        String reqDate = TemporalAccessorUtil.format(
                createTime.atZone(ZoneId.systemDefault()), PURE_DATE_PATTERN
        );
        request.setOrgReqDate(reqDate);
        // 原交易返回的全局流水号原交易返回的全局流水号、原交易请求流水号二选一必填；&lt;br/&gt;&lt;font color&#x3D;&quot;green&quot;&gt;示例值：00470topo1A211015160805P090ac132fef00000&lt;/font&gt;
//        request.setOrgHfSeqId("003000TOP1A250814011249P988ac139d2e00000");
        // 原交易请求流水号（与原交易返回的全局流水号二选一必填，这里使用 outTradeNo）
        request.setOrgReqSeqId(outTradeNo);

        // 3. 发起API调用
        Map<String, Object> response = doExecute(request);
        log.info("[汇付——获得转账订单信息成功][结果参数({})]", response);

        // 3. 处理响应结果
        if (isSuccess(response)) {
            String transStat = Convert.toStr(response.get("trans_status")); // 交易状态（P-处理中、S-成功、F-失败）
            String hfSeqId = Convert.toStr(response.get("org_hf_seq_id"));      // 汇付返回的全局流水号
            //  String endTime = Convert.toStr(response.get("end_time"));       // 原返回的交易完成时间（弃用）
            String endTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); // 当前系统时间作为交易完成时间
            String reqSeq = Convert.toStr(response.get("org_req_seq_id"));      // 原机构请求流水号
            String respCode = Convert.toStr(response.get("resp_code")); // 业务返回码
            String respDesc = Convert.toStr(response.get("resp_desc")); // 业务返回描述
            if ("P".equals(transStat)) { // 处理中
                return PayTransferRespDTO.dealingOf(hfSeqId, reqSeq, response);
            } else if ("S".equals(transStat)) { // 成功
                return PayTransferRespDTO.successOf(hfSeqId, parseDate(endTime), reqSeq, response);
            } else if ("F".equals(transStat)) { // 失败
                return PayTransferRespDTO.closedOf(respCode, respDesc, reqSeq, response);
            } else {
                throw new ServiceException(500, "【转账回调】交易状态无效: " + transStat);
            }
        }
        // 5. 如果响应不成功，则抛出异常
        throw new ServiceException(500, "【转账查询】响应失败，返回参数: " + response);
    }

    /**
     * 汇付统一转账（余额支付 + 自动取现到银行卡）
     * 1. 先通过余额支付接口，将资金从平台账户划拨到用户在汇付的账户（先抵扣账户余额）；
     * 2. 再通过取现接口，将资金从用户在汇付的账户打到用户绑定的银行卡。
     */
    @Override
    protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws Throwable {
        // ========================= 0.前置处理 =========================
        // 1. 金额转换（分 -> 元，保留两位小数）
        String ordAmtStr = convertPriceToYuan(reqDTO.getPrice());
        BigDecimal totalAmt = new BigDecimal(ordAmtStr); // 转账总金额（已扣手续费）
        BigDecimal payAmt = totalAmt;                   // 实际需要支付的金额，可能被余额抵扣

        // 请求流水号（保持在支付和取现两步中一致,方便链路追踪）
        String reqSeqId = reqDTO.getOutTransferNo();
        // 请求日期（yyyyMMdd）
        String reqDate = DateTools.getCurrentDateYYYYMMDD();

        // ========================= 1. 查询 DM 取现额度 =========================
        V2TradeSettlementEnchashmentDmamtQueryRequest dmamtQueryRequest = new V2TradeSettlementEnchashmentDmamtQueryRequest();
        dmamtQueryRequest.setHuifuId(config.getHuifuId());
        // 发起 DM 取现额度查询
        Map<String, Object> dmamtResp = doExecute(dmamtQueryRequest);
        log.info("[汇付——查询 DM 取现额度成功][结果参数({})]", dmamtResp);
        // 判断查询接口是否成功
        if (!isSuccess(dmamtResp)) {
            return PayTransferRespDTO.closedOf(Convert.toStr(dmamtResp.get("resp_code")),
                    Convert.toStr(dmamtResp.get("resp_desc")), reqSeqId, dmamtResp);
        }
        // 拿出对应的额度
        BigDecimal dmAmt = new BigDecimal(Convert.toStr(dmamtResp.get("dm_amt")));
        // 如果dmAmt额度小于totalAmt就返回错误
        if (dmAmt.compareTo(totalAmt) < 0) {
            String errMsg = String.format("DM额度不足：可用额度 = %s，请求金额 = %s", dmAmt, totalAmt);
            log.warn(errMsg);
            return PayTransferRespDTO.closedOf(Convert.toStr(dmamtResp.get("resp_code")),
                    errMsg, reqSeqId, dmamtResp);
        }
        // ========================= 2. 查询账户余额信息阶段 =========================
        V2TradeAcctpaymentBalanceQueryRequest request = new V2TradeAcctpaymentBalanceQueryRequest();
        request.setReqDate(reqDate);                          // 请求日期
        request.setHuifuId(reqDTO.getHuifuId());              // 打款的商户号（汇付ID）
        // 设置非必填字段
        Map<String, Object> extendInfoMap = new HashMap<>();
        extendInfoMap.put("req_seq_id", reqSeqId); // 请求流水号
        request.setExtendInfo(extendInfoMap);
        // 发起账户余额查询
        Map<String, Object> balResp = doExecute(request);
        log.info("[汇付——查询账户余额信息成功][结果参数({})]", balResp);

        // 判断查询接口是否成功
        if (!isSuccess(balResp)) {
            return PayTransferRespDTO.closedOf(Convert.toStr(balResp.get("resp_code")),
                    Convert.toStr(balResp.get("resp_desc")), reqSeqId, balResp);
        }

        // 解析账户余额信息（acctInfo_list 是字符串形式的 JSON 数组）
        String acctInfoStr = Convert.toStr(balResp.get("acctInfo_list"));
        JSONArray acctInfoArray = JSON.parseArray(acctInfoStr);

        if (acctInfoArray == null || acctInfoArray.isEmpty()) {
            log.error("账户余额列表为空，无法校验余额！");
            return PayTransferRespDTO.closedOf(Convert.toStr(balResp.get("resp_code")),
                    "账户余额列表为空，无法校验余额！", reqSeqId, balResp);
        }

        JSONObject acctInfo = acctInfoArray.getJSONObject(0);

        // 获取账户余额和可用余额
        String balanceAmtStr = acctInfo.getString("balance_amt");
        String avlBalStr = acctInfo.getString("avl_bal");

        // 转换为 BigDecimal 避免浮点精度问题
        BigDecimal balanceAmt = new BigDecimal(balanceAmtStr);
        BigDecimal avlBal = new BigDecimal(avlBalStr);

        // 判断账户余额和可用余额是否一致，并且是否大于 0
        if (balanceAmt.compareTo(avlBal) == 0 && avlBal.compareTo(BigDecimal.ZERO) > 0) {
            log.info("余额校验通过，账户余额 balance_amt = {}，可用余额 avl_bal = {}", balanceAmt, avlBal);
            payAmt = totalAmt.subtract(avlBal); // 余额抵扣后剩余需要支付的金额
            // 如果余额已足够覆盖支付，则不需要额外支付
            if (payAmt.compareTo(BigDecimal.ZERO) < 0) {
                payAmt = BigDecimal.ZERO;
            }
        } else {
            log.warn("账户余额或可用余额为空，跳过余额抵扣逻辑：账户余额 = {}，可用余额 = {}", balanceAmt, avlBal);
//            return PayTransferRespDTO.closedOf(Convert.toStr(response.get("resp_code")),
//                    "余额校验失败,账户余额和可用余额不匹配", reqSeqId, response);
        }

        // ========================= 3. 余额支付阶段 =========================
        if (payAmt.compareTo(BigDecimal.ZERO) > 0) { // 需要支付
            V2TradeAcctpaymentPayRequest payRequest = buildPayRequest(reqDTO,
                    payAmt.setScale(2, RoundingMode.HALF_UP).toPlainString(),
                    reqSeqId, reqDate);
            // 调用余额支付接口
            Map<String, Object> payResponse = doExecute(payRequest);
            log.info("[汇付——余额支付成功][结果参数({})]", payResponse);

            // 判断余额支付接口是否成功返回
            if (!isSuccess(payResponse)) {
                return PayTransferRespDTO.closedOf(Convert.toStr(payResponse.get("resp_code")),
                        Convert.toStr(payResponse.get("resp_desc")), reqSeqId, payResponse);
            }
            // 判断交易状态是否为成功
            if (!isTransSuccess(payResponse)) {
                return PayTransferRespDTO.closedOf(Convert.toStr(payResponse.get("resp_code")),
                        Convert.toStr(payResponse.get("resp_desc")), reqSeqId, payResponse);
            }
        } else {
            log.info("本次支付金额为 0，跳过余额支付阶段，直接进入取现阶段。");
        }
        // ========================= 4. 取现阶段 =========================
        // 构造取现请求
        V2TradeSettlementEnchashmentRequest withdrawRequest = buildWithdrawRequest(reqDTO,
                totalAmt.setScale(2, RoundingMode.HALF_UP).toPlainString(),
                reqSeqId, reqDate);
        // 发起API调用
        Map<String, Object> withdrawResponse = doExecute(withdrawRequest);
        log.info("[汇付——余额转账成功][结果参数({})]", withdrawResponse);

        // 判断取现接口是否成功返回
        if (!isSuccess(withdrawResponse)) {
            return PayTransferRespDTO.closedOf(Convert.toStr(withdrawResponse.get("resp_code")),
                    Convert.toStr(withdrawResponse.get("resp_desc")), reqSeqId, withdrawResponse);
        }
        // 取现交易状态
        String transStat = Convert.toStr(withdrawResponse.get("trans_stat"));
        String hfSeqId = Convert.toStr(withdrawResponse.get("hf_seq_id")); // 汇付返回的全局流水号
        // 取当前系统时间作为交易完成时间
        String endTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        String reqSeq = Convert.toStr(withdrawResponse.get("req_seq_id"));  // 原机构请求流水号
        // 根据交易状态返回不同的结果对象
        if ("P".equals(transStat)) { // 处理中
            return PayTransferRespDTO.dealingOf(hfSeqId, reqSeq, withdrawResponse);
        } else if ("S".equals(transStat)) { // 成功
            return PayTransferRespDTO.successOf(hfSeqId, parseDate(endTime), reqSeq, withdrawResponse);
        } else {
            return PayTransferRespDTO.closedOf(Convert.toStr(withdrawResponse.get("resp_code")),
                    Convert.toStr(withdrawResponse.get("resp_desc")), reqSeqId, withdrawResponse);
        }
    }

    /**
     * 构建余额支付请求对象
     */
    private V2TradeAcctpaymentPayRequest buildPayRequest(PayTransferUnifiedReqDTO reqDTO, String ordAmt, String reqSeqId, String reqDate) {
        V2TradeAcctpaymentPayRequest request = new V2TradeAcctpaymentPayRequest();
        request.setReqSeqId(reqSeqId); // 请求流水号
        request.setReqDate(reqDate);   // 请求日期
        request.setOutHuifuId(config.getHuifuId()); // 出款方商户号
        request.setOrdAmt(ordAmt); // 支付金额（元）

        // 分账对象
        JSONObject splitInfo = new JSONObject();
        splitInfo.put("div_amt", ordAmt); // 分账金额
        splitInfo.put("huifu_id", reqDTO.getHuifuId()); // 分账接收方ID
        JSONArray splitList = new JSONArray();
        splitList.add(splitInfo);

        JSONObject acctSplitBunch = new JSONObject();
        acctSplitBunch.put("acct_infos", splitList);
        request.setAcctSplitBunch(acctSplitBunch.toJSONString());

        // 风控信息
        JSONObject riskData = new JSONObject();
        riskData.put("transfer_type", "04"); // 转账原因
        riskData.put("sub_product", "1");    // 产品子类
        request.setRiskCheckData(riskData.toJSONString());

        return request;
    }

    /**
     * 构建取现请求对象
     */
    private V2TradeSettlementEnchashmentRequest buildWithdrawRequest(PayTransferUnifiedReqDTO reqDTO, String ordAmt, String reqSeqId, String reqDate) {
        V2TradeSettlementEnchashmentRequest request = new V2TradeSettlementEnchashmentRequest();
        request.setReqDate(reqDate);     // 请求日期
        request.setReqSeqId(reqSeqId);   // 请求流水号
        request.setCashAmt(ordAmt);      // 取现金额
        request.setHuifuId(reqDTO.getHuifuId()); // 商户号（提现发起方的汇付ID）
        request.setIntoAcctDateType("DM");       // 到账日期类型（DM=当日到账）
        request.setTokenNo(reqDTO.getTokenNo()); // 取现卡序列号

        // 非必填字段
        Map<String, Object> extendInfoMap = new HashMap<>();
        extendInfoMap.put("notify_url", reqDTO.getNotifyUrl()); // 异步通知地址（配置化）
        request.setExtendInfo(extendInfoMap);

        return request;
    }

    /**
     * 判断接口返回是否成功（根据 resp_code）
     */
    private boolean isSuccess(Map<String, Object> response) {
        return "00000000".equals(Convert.toStr(response.get("resp_code")));
    }

    /**
     * 判断交易状态是否成功（根据 trans_stat）
     */
    private boolean isTransSuccess(Map<String, Object> response) {
        return "S".equals(Convert.toStr(response.get("trans_stat")));
    }


    /***
     * @Description: 执行API调用
     * @param request 请求参数
     * @throws Exception  Map<String,Object>
     * @date: 2021年7月5日17:59:37
     */
    public static Map<String, Object> doExecute(BaseRequest request) throws Exception {
        return BasePayClient.request(request, false);
    }

    private String convertPriceToYuan(Integer price) {
        return BigDecimal.valueOf(price)
                .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)
                .toPlainString();
    }

    static String formatDateToYYYYMMDDHHMMSS(LocalDateTime time) {
        return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN);
    }

    static String formatDateToYYYYMMDD(LocalDateTime time) {
        return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATE_PATTERN);
    }

    /**
     * 获取openid
     */
    static String getOpenid(PayOrderUnifiedReqDTO reqDTO) {
        String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid");
        if (StrUtil.isEmpty(openid)) {
            throw invalidParamException("支付请求的 openid 不能为空！");
        }
        return openid;
    }

    @Override
    protected UserBasicdataRespDTO doCreateUserBasicdataIndv(UserInfoReqDTO userInfoReqDTO) throws Throwable {
        V2UserBasicdataIndvRequest request = new V2UserBasicdataIndvRequest();
        request.setReqSeqId(SequenceTools.getReqSeqId32()); // 请求流水号
        request.setReqDate(DateTools.getCurrentDateYYYYMMDD()); // 请求日期
        request.setName(userInfoReqDTO.getRealName()); // 个人姓名
        request.setCertType("00"); // 个人证件类型
        request.setCertNo(userInfoReqDTO.getIdNumber()); // 个人证件号码
        request.setCertValidityType("1"); // 个人证件有效期类型 1:长期有效 0:非长期有效
        String idStartDateStr = formatDateToYYYYMMDD(userInfoReqDTO.getIdStartDate().atStartOfDay());
        request.setCertBeginDate(idStartDateStr); // 个人证件有效期开始日期
        request.setMobileNo(userInfoReqDTO.getMobileNo()); // 手机号

        // 设置非必填字段
        Map<String, Object> extendInfoMap = new HashMap<>();
        // 个人证件有效期截止日期
        // TODO 这个地方所有的人的身份证都是长期有效，所以这里设置一个 50 年的截止日期
        // 截止日期 = 开始日期 + 50 年
        String idEndDateStr = formatDateToYYYYMMDD(
                userInfoReqDTO.getIdStartDate().plusYears(50).atStartOfDay()
        );
        extendInfoMap.put("cert_end_date", idEndDateStr);
        // 是否发送短信标识
        extendInfoMap.put("sms_send_flag", "N");
        request.setExtendInfo(extendInfoMap);
        Map<String, Object> response = doExecute(request);
        log.info("[汇付创建用户基本数据][结果参数({})]", response);
//        String respCode = Convert.toStr(response.get("resp_code"));
//        if (ObjUtil.equal(respCode, "00000000")) {
//            return Convert.toStr(response.get("huifu_id"));
//        }
        UserBasicdataRespDTO dto = new UserBasicdataRespDTO();
        dto.setRespCode(Convert.toStr(response.get("resp_code")));
        dto.setRespDesc(Convert.toStr(response.get("resp_desc")));
        dto.setHuifuId(Convert.toStr(response.get("huifu_id")));

        return dto;
    }

    @Override
    protected UserBusiOpenRespDTO doUserBusiOpen(UserInfoReqDTO userInfoReqDTO) throws Throwable {
        V2UserBusiOpenRequest request = new V2UserBusiOpenRequest();
        request.setHuifuId(userInfoReqDTO.getUserHuifuId());        // 开户时返回汇付ID
        request.setReqSeqId(SequenceTools.getReqSeqId32());        // 请求流水号
        request.setReqDate(DateTools.getCurrentDateYYYYMMDD());        // 请求日期
        request.setUpperHuifuId(config.getHuifuId());        // 渠道商/商户汇付Id
        // 设置非必填字段
        Map<String, Object> extendInfoMap = new HashMap<>();
        // 结算卡信息
        Map<String, Object> cardInfoMap = new HashMap<>();
        cardInfoMap.put("card_type", "1");        // 对私非法人
        cardInfoMap.put("card_name", userInfoReqDTO.getRealName());
        cardInfoMap.put("card_no", userInfoReqDTO.getBankAccount());
        cardInfoMap.put("prov_id", "110000");
        cardInfoMap.put("area_id", "110100");

        cardInfoMap.put("cert_type", "00");// 持卡人证件类型
        cardInfoMap.put("cert_no", userInfoReqDTO.getIdNumber()); //持卡人证件号码
        cardInfoMap.put("cert_validity_type", "1"); // 持卡人证件有效期类型1：长期有效；0：非长期有效
        String idStartDateStr = formatDateToYYYYMMDD(userInfoReqDTO.getIdStartDate().atStartOfDay());
        cardInfoMap.put("cert_begin_date", idStartDateStr);// 日期格式：yyyyMMdd，示例值：20110112
        // TODO 这个地方所有的人的身份证都是长期有效，所以这里设置一个 50 年的截止日期
        // 截止日期 = 开始日期 + 50 年
        String idEndDateStr = formatDateToYYYYMMDD(
                userInfoReqDTO.getIdStartDate().plusYears(50).atStartOfDay()
        );
        cardInfoMap.put("cert_end_date", idEndDateStr); // 日期格式yyyyMMdd，示例值：20110112
        cardInfoMap.put("mp", userInfoReqDTO.getMobileNo()); // 银行卡绑定手机号

        String cardInfoJson = JSON.toJSONString(cardInfoMap);
        extendInfoMap.put("card_info", cardInfoJson);

        // 取现配置列表
        Map<String, Object> cashInfoMap = new HashMap<>();
        cashInfoMap.put("cash_type", "DM");
        cashInfoMap.put("fix_amt", "0.00"); // 提现手续费 需保留小数点后两位，不收费请填写0.00
        cashInfoMap.put("fee_rate", "0.00"); //提现手续费率 需保留小数点后两位，取值范围[0.00,100.00]，不收费请填写0.00；
        // 放入一个数组中
        List<Map<String, Object>> cashInfoList = new ArrayList<>();
        cashInfoList.add(cashInfoMap);

        // 转为 JSON 数组格式的字符串
        String cashInfoJsonArray = JSON.toJSONString(cashInfoList);
        extendInfoMap.put("cash_config", cashInfoJsonArray);
        request.setExtendInfo(extendInfoMap);
        // API调用
        Map<String, Object> response = doExecute(request);
        log.info("[汇付-用户业务入驻][结果参数({})]", response);
//        String respCode = Convert.toStr(response.get("resp_code"));
//        if (ObjUtil.equal(respCode, "00000000")) {
//            // 取现卡序列号
//            return Convert.toStr(response.get("token_no"));
//        }
        UserBusiOpenRespDTO dto = new UserBusiOpenRespDTO();
        dto.setRespCode(Convert.toStr(response.get("resp_code")));
        dto.setRespDesc(Convert.toStr(response.get("resp_desc")));
        dto.setHuifuId(Convert.toStr(response.get("huifu_id")));
        dto.setTokenNo(Convert.toStr(response.get("token_no")));

        return dto;
    }

    @Override
    protected UserBusiOpenRespDTO doUpdateuserBusiOpen(UserInfoReqDTO userInfoReqDTO) throws Throwable {
        V2UserBusiModifyRequest request = new V2UserBusiModifyRequest();
        request.setReqSeqId(SequenceTools.getReqSeqId32());        // 请求流水号
        request.setReqDate(DateTools.getCurrentDateYYYYMMDD());        // 请求日期
        request.setUpperHuifuId(config.getHuifuId());        // 渠道商汇付Id
        request.setHuifuId(userInfoReqDTO.getUserHuifuId());        // 汇付ID
        // 设置非必填字段
        Map<String, Object> extendInfoMap = new HashMap<>();
        // 结算卡信息
        Map<String, Object> cardInfoMap = new HashMap<>();
        cardInfoMap.put("card_type", "1");        // 对私非法人
        cardInfoMap.put("card_name", userInfoReqDTO.getRealName());
        cardInfoMap.put("card_no", userInfoReqDTO.getBankAccount());
        cardInfoMap.put("prov_id", "110000");
        cardInfoMap.put("area_id", "110000");

        cardInfoMap.put("cert_type", "00");// 持卡人证件类型
        cardInfoMap.put("cert_no", userInfoReqDTO.getIdNumber()); //持卡人证件号码
        cardInfoMap.put("cert_validity_type", "1"); // 持卡人证件有效期类型1：长期有效；0：非长期有效
        String idStartDateStr = formatDateToYYYYMMDD(userInfoReqDTO.getIdStartDate().atStartOfDay());
        cardInfoMap.put("cert_begin_date", idStartDateStr);// 日期格式：yyyyMMdd，示例值：20110112
        // 截止日期 = 开始日期 + 50 年
        String idEndDateStr = formatDateToYYYYMMDD(
                userInfoReqDTO.getIdStartDate().plusYears(50).atStartOfDay()
        );
        cardInfoMap.put("cert_end_date", idEndDateStr); // 日期格式yyyyMMdd，示例值：20110112
        cardInfoMap.put("mp", userInfoReqDTO.getMobileNo());        // 银行卡绑定手机号
        String cardInfoJson = JSON.toJSONString(cardInfoMap);
        extendInfoMap.put("card_info", cardInfoJson);

        // 取现配置列表
        Map<String, Object> cashInfoMap = new HashMap<>();
        cashInfoMap.put("switch_state", "1");// 开通状态
        cashInfoMap.put("cash_type", "DM");
        cashInfoMap.put("fix_amt", "0.00");
        cashInfoMap.put("fee_rate", "0.00");
        // 放入一个数组中
        List<Map<String, Object>> cashInfoList = new ArrayList<>();
        cashInfoList.add(cashInfoMap);

        // 转为 JSON 数组格式的字符串
        String cashInfoJsonArray = JSON.toJSONString(cashInfoList);
        extendInfoMap.put("cash_config", cashInfoJsonArray);

        request.setExtendInfo(extendInfoMap);
        // 3. 发起API调用
        Map<String, Object> response = doExecute(request);
        log.info("[汇付-用户业务入驻修改][结果参数({})]", response);

        UserBusiOpenRespDTO dto = new UserBusiOpenRespDTO();
        dto.setRespCode(Convert.toStr(response.get("resp_code")));
        dto.setRespDesc(Convert.toStr(response.get("resp_desc")));
        dto.setHuifuId(Convert.toStr(response.get("huifu_id")));
        dto.setTokenNo(Convert.toStr(response.get("token_no")));

        return dto;
    }
}
